<!DOCTYPE html>
          <head>
        <meta charset="utf-8">
            
            <title>
                「WebSocket」使用Websocket实现消息推送(心跳) | ttdevs
            </title>
            <meta content="width=device-width, initial-scale=1" name="viewport">
            <meta name="theme-color" content="#4184f3">
            
            
            <link href="/favicon.ico" rel="icon"/>
            

            <link rel="stylesheet" href="/css/highlight.light.css">
            <link rel="stylesheet" href="/css/prism-customize.css">
            <link rel="stylesheet" href="/css/nav-icon.css">
            <link rel="stylesheet" href="/css/waves.min.css">
            <link rel="stylesheet" href="/css/jquery.tocify.css">
            <link rel="stylesheet" href="/css/main.css">
            <link rel="stylesheet" href="/css/nav-indicator.css">
            
  

  
  <!-- 谷歌统计 -->
  <script>
    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
    })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

    ga('create', 'UA-97465173-1', 'auto');
    ga('send', 'pageview');

  </script>
  
            </meta>
        </meta>
    </head>

    <body>
        <header>
            <!-- cover image or sth. -->
        </header>
        <div id="main" class="m-scene">
            
<div class="nav-wrapper">

    <div class="container">
        <nav>
            <div class="logo wave">
                <a href="/" id="logo">
                    ttdevs
                </a>
            </div>
            <div class="nav-toggle-icon" >
                <div class="material-hamburger">
                    <span>
                    </span>
                    <span>
                    </span>
                    <span>
                    </span>
                </div>
            </div>
            <div class="menu-wrapper">
                <div class="nav-indicator">
                </div>
                <ul class="menus">
                    
                     
                        <li>
                            <a class="wave " href="/">
                                首页
                            </a>
                        </li>
                     
                        <li>
                            <a class="wave " href="/archives">
                                归档
                            </a>
                        </li>
                     
                        <li>
                            <a class="wave " href="/about">
                                关于
                            </a>
                        </li>
                     
                    
                   
                </ul>
            </div>
        </nav>
    </div>
</div>
            <div class="container content">
                <div class="scene_element scene_element--fadein">
                    <div class="row">
    <div class="main">
        <article>
          
          <header class="post-header with-cover" style="background-image:url('/1970/01/01/[WebSocket]使用Websocket实现消息推送(心跳)/cover.jpg')" >
          
          </header>
          <h1 class="post-title">「WebSocket」使用Websocket实现消息推送(心跳)</h1>

          <section class="post-info">
            <span class="post-date">1970/01/01</span>
            
            <span class="post-category">
                <a class="article-category-link" href="/categories/技术/">技术</a>
            </span>
            
            
            <span class="post-tags">
              <ul class="post-tag-list"><li class="post-tag-list-item"><a class="post-tag-list-link" href="/tags/WebSocket/">WebSocket</a></li></ul>
            </span>
            
          </section>

          <section class="post-content">
            <h2 id="0x00-心跳"><a href="/1970/01/01/[WebSocket]使用Websocket实现消息推送(心跳)/#0x00-心跳" class="headerlink" title="0x00 心跳"></a>0x00 心跳</h2><p>本来以为写完了，结果最近和一个同事在讨论心跳的事情，这里再做一个补充。先说我的结论：</p>
<ul>
<li>WebSocket协议已经设计了心跳，这个功能可以到达检测链接是否可用</li>
<li>心跳是用来检测链接是否可用的，不一定支持携带数据，可要看具体实现</li>
<li>如果非要心跳中带上复杂数据，那这个可作为应用层的一个功能自己去实现。</li>
</ul>
<p><img src="http://img.blog.csdn.net/20170317140005670?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdHRkZXZz/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="心跳逻辑"></p>
<h2 id="0x01-WebSocket协议的控制帧"><a href="/1970/01/01/[WebSocket]使用Websocket实现消息推送(心跳)/#0x01-WebSocket协议的控制帧" class="headerlink" title="0x01 WebSocket协议的控制帧"></a>0x01 WebSocket协议的控制帧</h2><p>上一篇的最后简单提到了心跳，下面是对websocket协议控制帧的描述：</p>
<pre><code>5.5.  Control Frames

   Control frames are identified by opcodes where the most significant
   bit of the opcode is 1.  Currently defined opcodes for control frames
   include 0x8 (Close), 0x9 (Ping), and 0xA (Pong).  Opcodes 0xB-0xF are
   reserved for further control frames yet to be defined.

   Control frames are used to communicate state about the WebSocket.
   Control frames can be interjected in the middle of a fragmented
   message.

   All control frames MUST have a payload length of 125 bytes or less
   and MUST NOT be fragmented.
</code></pre><ul>
<li>Ping的协议头是0x9，Pong的协议头是0xA</li>
<li>控制帧最大载荷为125bytes且不能拆分</li>
</ul>
<h2 id="0x02-WebSocket协议的心跳"><a href="/1970/01/01/[WebSocket]使用Websocket实现消息推送(心跳)/#0x02-WebSocket协议的心跳" class="headerlink" title="0x02 WebSocket协议的心跳"></a>0x02 WebSocket协议的心跳</h2><p>下面再来看看对心跳的规定：</p>
<pre><code>5.5.2.  Ping

   The Ping frame contains an opcode of 0x9.

   A Ping frame MAY include &quot;Application data&quot;.
   // 注：Ping帧中可能会携带数据

   Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in
   response, unless it already received a Close frame.  It SHOULD
   respond with Pong frame as soon as is practical.  Pong frames are
   discussed in Section 5.5.3.
   // 注：在收到Ping帧后，端点必须发送Pong帧响应，除非已经收到了Close帧。在实际中应尽可能快的响应。

   An endpoint MAY send a Ping frame any time after the connection is
   established and before the connection is closed.

   NOTE: A Ping frame may serve either as a keepalive or as a means to
   verify that the remote endpoint is still responsive.

5.5.3.  Pong

   The Pong frame contains an opcode of 0xA.

   Section 5.5.2 details requirements that apply to both Ping and Pong
   frames.

   A Pong frame sent in response to a Ping frame must have identical
   &quot;Application data&quot; as found in the message body of the Ping frame
   being replied to.
   // 注：在响应Ping帧的的Pong帧中，必须携和被响应的Ping帧中相同的数据。

   If an endpoint receives a Ping frame and has not yet sent Pong
   frame(s) in response to previous Ping frame(s), the endpoint MAY
   elect to send a Pong frame for only the most recently processed Ping
   frame.
</code></pre><p>从上面的描述我们可以得到如下结论：</p>
<ul>
<li>心跳包中可能会携带数据</li>
<li>当收到Ping帧的时候需要立即返回一个Pong帧</li>
<li>在连接建立之后，随时都可以发送Ping帧</li>
<li>心跳是用来测试链接是否存在和对方是否在线</li>
<li>在响应Ping帧的的Pong帧中，必须携和被响应的Ping帧中相同的数据</li>
</ul>
<h2 id="0x03-测试"><a href="/1970/01/01/[WebSocket]使用Websocket实现消息推送(心跳)/#0x03-测试" class="headerlink" title="0x03 测试"></a>0x03 测试</h2><p>和之前一样，自己本地搭建的服务器，用的库是 <code>org.java_websocket</code>。<br>在其源码中我们可以找到这样一段：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">package</span> org.java_websocket;</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">WebSocketAdapter</span> <span class="keyword">implements</span> <span class="title">WebSocketListener</span> </span>&#123;</div><div class="line">    ...</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onWebsocketPing</span><span class="params">(WebSocket conn, Framedata f)</span> </span>&#123;</div><div class="line">        FramedataImpl1 resp = <span class="keyword">new</span> FramedataImpl1(f);</div><div class="line">        resp.setOptcode(Opcode.PONG);</div><div class="line">        conn.sendFrame(resp);</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onWebsocketPong</span><span class="params">(WebSocket conn, Framedata f)</span> </span>&#123;</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    ...</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>客户端也可以使用这个库，相同的逻辑，代码也是这一份。</p>
<p>然后我们再换一个库，<code>com.squareup.okhttp3:okhttp-ws:3.4.2</code>，他的实现如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div></pre></td><td class="code"><pre><div class="line"></div><div class="line"><span class="keyword">package</span> okhttp3.internal.ws;</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">RealWebSocket</span> <span class="keyword">implements</span> <span class="title">WebSocket</span> </span>&#123;</div><div class="line">  ...</div><div class="line">  <span class="function"><span class="keyword">public</span> <span class="title">RealWebSocket</span><span class="params">(<span class="keyword">boolean</span> isClient, BufferedSource source, BufferedSink sink, Random random,</span></span></div><div class="line">      <span class="keyword">final</span> Executor replyExecutor, <span class="keyword">final</span> WebSocketListener listener, <span class="keyword">final</span> String url) &#123;</div><div class="line">    <span class="keyword">this</span>.listener = listener;</div><div class="line"></div><div class="line">    writer = <span class="keyword">new</span> WebSocketWriter(isClient, sink, random);</div><div class="line">    reader = <span class="keyword">new</span> WebSocketReader(isClient, source, <span class="keyword">new</span> FrameCallback() &#123;</div><div class="line">      <span class="meta">@Override</span> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onMessage</span><span class="params">(ResponseBody message)</span> <span class="keyword">throws</span> IOException </span>&#123;</div><div class="line">        listener.onMessage(message);</div><div class="line">      &#125;</div><div class="line"></div><div class="line">      <span class="meta">@Override</span> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onPing</span><span class="params">(<span class="keyword">final</span> Buffer buffer)</span> </span>&#123;</div><div class="line">        replyExecutor.execute(<span class="keyword">new</span> NamedRunnable(<span class="string">"OkHttp %s WebSocket Pong Reply"</span>, url) &#123;</div><div class="line">          <span class="meta">@Override</span> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">execute</span><span class="params">()</span> </span>&#123;</div><div class="line">            <span class="keyword">try</span> &#123;</div><div class="line">              writer.writePong(buffer);</div><div class="line">            &#125; <span class="keyword">catch</span> (IOException ignored) &#123;</div><div class="line">            &#125;</div><div class="line">          &#125;</div><div class="line">        &#125;);</div><div class="line">      &#125;</div><div class="line"></div><div class="line">      <span class="meta">@Override</span> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onPong</span><span class="params">(Buffer buffer)</span> </span>&#123;</div><div class="line">        listener.onPong(buffer);</div><div class="line">      &#125;</div><div class="line"></div><div class="line">      <span class="meta">@Override</span> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onClose</span><span class="params">(<span class="keyword">final</span> <span class="keyword">int</span> code, <span class="keyword">final</span> String reason)</span> </span>&#123;</div><div class="line">        readerSentClose = <span class="keyword">true</span>;</div><div class="line">        replyExecutor.execute(<span class="keyword">new</span> NamedRunnable(<span class="string">"OkHttp %s WebSocket Close Reply"</span>, url) &#123;</div><div class="line">          <span class="meta">@Override</span> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">execute</span><span class="params">()</span> </span>&#123;</div><div class="line">            peerClose(code, reason);</div><div class="line">          &#125;</div><div class="line">        &#125;);</div><div class="line">      &#125;</div><div class="line">    &#125;);</div><div class="line">  &#125;</div><div class="line">...</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>在处理Ping帧的时候，也是将协议字段改为Pong然后返回。</p>
<p>对心跳的测试代码已经上传到Github：<a href="https://github.com/ttdevs" target="_blank" rel="external">ttdevs</a><br><a href="https://github.com/ttdevs/android/blob/master/app/src/main/java/com/ttdevs/android/WebSocketActivity.java" target="_blank" rel="external">WebSocketActivity.java</a><br><a href="https://github.com/ttdevs/android/blob/master/app/src/main/java/com/ttdevs/android/WebSocketOKActivity.java" target="_blank" rel="external">WebSocketOKActivity.java</a><br><a href="https://github.com/ttdevs/android/blob/master/modules/webscoket/src/main/java/com/ttdevs/webscoket/SocketServer.java" target="_blank" rel="external">SocketServer.java</a></p>
<p>在实际的测试中，可能会遇到一些异常，比如在我们自己的生产环境：当客户端发送带了简单数据的Ping帧后，服务器立马返回Pong帧，但是它会将携带的数据丢弃。这个就是服务端的问题了。</p>
<h2 id="0xFF-参考"><a href="/1970/01/01/[WebSocket]使用Websocket实现消息推送(心跳)/#0xFF-参考" class="headerlink" title="0xFF 参考"></a>0xFF 参考</h2><ol>
<li>RFC6455: <a href="https://datatracker.ietf.org/doc/rfc6455/" target="_blank" rel="external">https://datatracker.ietf.org/doc/rfc6455/</a></li>
</ol>
<p><img src="https://raw.githubusercontent.com/ttdevs/ttdevs.github.io/common/images/logo.png" alt="Create by ttdevs"></p>

          </section>
        </article>
        

       
        <div class="pager">
          
            <a class="post-prev pager-item" href="/1970/01/01/[WebSocket]使用Websocket实现消息推送(下)/" >
              <strong class="article-nav-caption">上一篇</strong>
              <p class="post-nav-title">「WebSocket」使用Websocket实现消息推送(下)</p>
            </a>
          
          
            <a class="post-next pager-item" href="/1970/01/01/[macOS]用Homebrew安装Mysql/">
              <strong class="article-nav-caption">下一篇</strong>
              <p class="post-nav-title">「macOS」用Homebrew安装Mysql</p>
            </a>
          
        </div>
        

         <!-- comments -->
        <div class="comment-section">
  
    


</div>

    </div>
    
    <aside>
        <div id="toc">
        </div>
    </aside>
    
</div>

                </div>
            </div>
        </div>
        <footer class="footer">
    <p>由<a href="http://hexo.io/" target="_blank">Hexo</a>强力驱动，搭载<a href="https://github.com/wayou/hexo-theme-gstyle">gstyle</a>主题</p>
    <p>
        &copy; 2017 ttdevs
    </p>
</footer>
<script src="/lib/jquery.js"></script>
<script src="/lib/waves.js"></script>
<script src="/lib/jquery-ui.js"></script>
<script src="/lib/jquery.tocify.js"></script>
<script src="/js/main.js"></script>

    </body>
</html>
